/* 
	declaration of job structures
*/

#ifndef __SPU_JOBSTRUCTS_H
#define __SPU_JOBSTRUCTS_H
#pragma once

#include <IJobManager.h>













//forward declarations for friend usage
namespace JobManager
{		
	namespace detail
	{
		//triple buffer frame stats
		enum { eJOB_FRAME_STATS = 3 };
	
		// configuration for job queue sizes:
		// we have tree types of backends, SPU, threads and blocking threads
		// each jobqueue has three priority levels, the first value is high priority, then regular, followed by low priority



























#if defined(WIN32) || defined(WIN64) || defined(gringo) || defined(LINUX) || defined(CAFE)
		// equals 7040 always allocated jobs = 1760 KB
		static const unsigned int cMaxWorkQueueJobs_SPUBackEnd_HighPriority					= 0;
		static const unsigned int cMaxWorkQueueJobs_SPUBackEnd_RegularPriority			= 0;
		static const unsigned int cMaxWorkQueueJobs_SPUBackEnd_LowPriority					= 0;

		static const unsigned int cMaxWorkQueueJobs_ThreadBackEnd_HighPriority			= 512;
		static const unsigned int cMaxWorkQueueJobs_ThreadBackEnd_RegularPriority		= 4096;
		static const unsigned int cMaxWorkQueueJobs_ThreadBackEnd_LowPriority				= 2048;

		static const unsigned int cMaxWorkQueueJobs_BlockingBackEnd_HighPriority		= 64;
		static const unsigned int cMaxWorkQueueJobs_BlockingBackEnd_RegularPriority	= 256;
		static const unsigned int cMaxWorkQueueJobs_BlockingBackEnd_LowPriority			= 64;
#else
		#error JobQueueSize not configurated for this platform
#endif

	} // namespace detail


	//dedicated custom callback data for each spu
	struct SDMACallbackData
	{
		JobManager::SCallback callbackData;							//callback data, 8 bytes
		volatile unsigned int transferStatus;			//0 if in progress
		unsigned int pad;
		ILINE SDMACallbackData()
		{
			SVEC4_UINT temp = {0};
			*((SVEC4_UINT*)this) = temp;
		}














		void Wait()
		{
			uint32 loopCount = 0;
			while(transferStatus == 0)
			{
				for(uint32 i=0; i<16; ++i)
				{






				}
				++loopCount;
				if(loopCount > 16)break;
			}
			while(transferStatus == 0)
			{



			}
		}

	} _ALIGN(16);
}

namespace NSPU
{
	namespace NDriver
	{
		//performance stats for job
		//cacheHits must be first element for SPU speed processing
		//do not reorder, accessed in asm code
		struct SJobPerfStats
		{
			//always leave cacheHits as first component, incremented directly by address in SPUJob.h
			unsigned int cacheHits;							//cache hits
			unsigned int spuJobTime;						//time for the job itself (in ticks)
			unsigned int allocSize;							//allocation amount
			unsigned int totalBucketAllocSize;	//effective bucket allocation amount

			unsigned int cacheMisses;						//cache misses
			unsigned int cacheWritesBackSync;		//number of cache lines transferred back synchronously (became dirty)
			unsigned int cacheFlushsNoWrite;		//number of cache lines NOT transferred back (non dirty)
			unsigned int prefetchHits;					//prefetch hits

			unsigned int prefetchMisses;				//prefetch misses
			unsigned int cacheWritesBackASync;	//asynchronous write backs
			unsigned int memTransToLS;					//amount of cache memory transferred to LS
			unsigned int memTransFromLS;				//amount of cache memory transferred back from LS

			unsigned short spuSetupTime;				//job time without job function (in ticks)
			unsigned short allocsBucket;				//allocations in job handled by bucket system
			unsigned short lostLineEvents;			//number of lost Line events for Cache write backs
			unsigned short allocsNoBucket;			//allocations in job not handled by bucket system
			unsigned short driverSize;					//size of driver in 16 byte chunks
			unsigned short jobSize;							//size of job in 16 byte chunks
			unsigned short freeCount;						//number of memory releases into bucket allocator
			unsigned short freeCountHistory;		//number of memory releases matching history table

			unsigned int spuCacheMissTime;			//time for the job spent in the cache miss handler
			unsigned short freeCountNoBucket;		//number of memory releases outside bucket allocator
			unsigned short spuFetchTime;				//time between job fetch and call to ProcessJob
			unsigned short atomicOps;						//number of atomic operations issued
			unsigned short firstPageSize;				//size of first page in 16 bytes
			unsigned short pad0;
			unsigned char  stackSize;						//size of stack for job (in KB)
			unsigned char  cacheSize;						//cache setup for job (in KB)

			unsigned int pageMemTransferred;		//page memory transferred in bytes
			unsigned short returnMissHandlerCalls;//number of calls into return miss handler
			unsigned short callMissHandlerCalls;//number of calls into call miss handler
			unsigned short pageMisses;						//non present pages in SetActivePages
			unsigned short syncsInCallMissHandler;//number of page transfer syncs in call miss handler
			unsigned short pageMissesRetMissHandler;//non present return pages in return miss handler
			unsigned short pagesTransferred;		//number of pages transferred in total to LS	

			unsigned int funcPtrHits;						//history table hits for function pointers and virt.functions
			unsigned int funcPtrMisses;					//history table misses for function pointers and virt.functions
			unsigned int pad1;
			unsigned int pad2;

			void Reset()
			{
				SVEC4_UINT temp = {0};
				((SVEC4_UINT*)this)[0] = temp;
				((SVEC4_UINT*)this)[1] = temp;
				((SVEC4_UINT*)this)[2] = temp;
				((SVEC4_UINT*)this)[3] = temp;
				((SVEC4_UINT*)this)[4] = temp;
				((SVEC4_UINT*)this)[5] = temp;
				((SVEC4_UINT*)this)[6] = temp;
			}
		} _ALIGN(16);
	}
}

namespace JobManager 
{
	// struct to manage the state of a job slot
	// used to indicate that a info block has been finished writing
	struct SJobQueueSlotState
	{
	public:
		bool IsReady() const			{ return m_state == READY; }		

		void SetReady()						{ m_state = READY; }
		void SetNotReady()				{ m_state = NOT_READY; }

	private:
		enum StateT {	NOT_READY = 0,
									READY = 1 };
		volatile StateT m_state;
	};
	
	//spu queue node where jobs are pushed into and pulled from
	//individual aligned because it is using MFC_LLAR atomics for mutual exclusive access(operates on a 128 byte address base)
	//dont waste memory by putting the job queue between the 2 aligned buffers
	template<int nMaxWorkQueueJobsHighPriority,int nMaxWorkQueueJobsRegularPriority, int nMaxWorkQueueJobsLowPriority>
	struct SJobQueue
	{		
		// store queue sizes to allow to query those
		enum {
			eMaxWorkQueueJobsHighPriority			= nMaxWorkQueueJobsHighPriority,
			eMaxWorkQueueJobsRegularPriority	= nMaxWorkQueueJobsRegularPriority,
			eMaxWorkQueueJobsLowPriority			= nMaxWorkQueueJobsLowPriority
		};

		_MS_ALIGN(128) JobManager::SJobQueuePos push _ALIGN(128);										// position in which jobs are pushed by the PPU		
		_MS_ALIGN(128) JobManager::SJobQueuePos pull _ALIGN(128);										// position from which jobs are pulled by the SPU, dont add anything in between
		
		JobManager::SInfoBlock *jobInfoBlocks[eNumPriorityLevel];										// aligned array of SInfoBlocks per priority level
		JobManager::SJobQueueSlotState *jobInfoBlockStates[eNumPriorityLevel];			// aligned array of SInfoBlocks states per priority level

		
		// initialize the jobqueue, should only be called once
		void Init();

		//gets job slot for next job (to get storage index for SJobdata), waits until a job slots becomes available again since data get overwritten
		JobManager::EAddJobRes GetJobSlot(uint32& RESTRICT_REFERENCE rJobSlot, uint32 nPriorityLevel, bool bWaitForFreeJobSlot);

		static uint32 GetMaxWorkerQueueJobs( uint32 nPriorityLevel );
	};


	///////////////////////////////////////////////////////////////////////////////
	// convinient typedef for the different platform queues
	typedef SJobQueue<JobManager::detail::cMaxWorkQueueJobs_SPUBackEnd_HighPriority, JobManager::detail::cMaxWorkQueueJobs_SPUBackEnd_RegularPriority, JobManager::detail::cMaxWorkQueueJobs_SPUBackEnd_LowPriority>									SJobQueue_SPUBackend;
	typedef SJobQueue<JobManager::detail::cMaxWorkQueueJobs_ThreadBackEnd_HighPriority, JobManager::detail::cMaxWorkQueueJobs_ThreadBackEnd_RegularPriority, JobManager::detail::cMaxWorkQueueJobs_ThreadBackEnd_LowPriority>					SJobQueue_ThreadBackEnd;
	typedef SJobQueue<JobManager::detail::cMaxWorkQueueJobs_BlockingBackEnd_HighPriority, JobManager::detail::cMaxWorkQueueJobs_BlockingBackEnd_RegularPriority, JobManager::detail::cMaxWorkQueueJobs_BlockingBackEnd_LowPriority>		SJobQueue_BlockingBackEnd;
}

///////////////////////////////////////////////////////////////////////////////
template<int nMaxWorkQueueJobsHighPriority,int nMaxWorkQueueJobsRegularPriority, int nMaxWorkQueueJobsLowPriority>
inline JobManager::EAddJobRes JobManager::SJobQueue<nMaxWorkQueueJobsHighPriority,nMaxWorkQueueJobsRegularPriority,nMaxWorkQueueJobsLowPriority>::GetJobSlot(uint32& RESTRICT_REFERENCE rJobSlot, uint32 nPriorityLevel, bool bWaitForFreeJobSlot)
{
	// verify assumation about queue size at compile time
#if !defined(__SPU__)
	STATIC_CHECK( IsPowerOfTwoCompileTime<eMaxWorkQueueJobsHighPriority>::IsPowerOfTwo, ERROR_MAX_JOB_QUEUE_SIZE__HIGH_PRIORITY_IS_NOT_POWER_OF_TWO );
	STATIC_CHECK( IsPowerOfTwoCompileTime<eMaxWorkQueueJobsRegularPriority>::IsPowerOfTwo, ERROR_MAX_JOB_QUEUE_SIZE_REGULAR_PRIORITY_IS_NOT_POWER_OF_TWO );
	STATIC_CHECK( IsPowerOfTwoCompileTime<eMaxWorkQueueJobsLowPriority>::IsPowerOfTwo, ERROR_MAX_JOB_QUEUE_SIZE__LOW_PRIORITY_IS_NOT_POWER_OF_TWO );
#endif




	//do changes in sync with SPU implementation in JobAPI_spu.cpp, wait until a job slot becomes available
	SJobQueuePos& RESTRICT_REFERENCE curPushEntry = push;			
									
	uint64 currentIndex;
	uint64 nextIndex;
	uint32 nRoundID;
	uint32 nExtractedIndex;
	JobManager::SInfoBlock *pPushInfoBlock = NULL;
	uint32 nMaxWorkerQueueJobs = GetMaxWorkerQueueJobs(nPriorityLevel);
	do
	{
		// fetch next to update field
		currentIndex	= curPushEntry.index; 		
		nextIndex = JobManager::SJobQueuePos::IncreasePushIndex(currentIndex, nPriorityLevel );
		nExtractedIndex = static_cast<uint32>(JobManager::SJobQueuePos::ExtractIndex(currentIndex, nPriorityLevel ));
		nRoundID	= nExtractedIndex / nMaxWorkerQueueJobs;

		// compute the job slot for this fetch index
		unsigned int jobSlot	= nExtractedIndex & (nMaxWorkerQueueJobs-1);												
		pPushInfoBlock = &curPushEntry.jobQueue[nPriorityLevel][jobSlot];

		//do not overtake pull pointer
		bool bWait = false;
		bool bRetry = false;
		pPushInfoBlock->IsInUse(nRoundID,bWait,bRetry, (1 << JobManager::SJobQueuePos::eBitsPerPriorityLevel) / nMaxWorkerQueueJobs );

		if(bRetry) // need to refetch due long suspending time
			continue;

		if( bWait )
		{
			if(bWaitForFreeJobSlot)
			{
				pPushInfoBlock->Wait(nRoundID,(1 << JobManager::SJobQueuePos::eBitsPerPriorityLevel) / nMaxWorkerQueueJobs);
			}
			else
				return eAJR_NeedFallbackJobInfoBlock;					
		}												
		
		rJobSlot = jobSlot;				

		if(CryInterlockedCompareExchange64(alias_cast<volatile int64*>(&curPushEntry.index),nextIndex,currentIndex) == currentIndex)
			break;

	} while (true);

	
	return JobManager::eAJR_Success;
}

///////////////////////////////////////////////////////////////////////////////
template<int nMaxWorkQueueJobsHighPriority,int nMaxWorkQueueJobsRegularPriority, int nMaxWorkQueueJobsLowPriority>
inline void JobManager::SJobQueue<nMaxWorkQueueJobsHighPriority,nMaxWorkQueueJobsRegularPriority,nMaxWorkQueueJobsLowPriority>::Init()
{					
#if !defined(__SPU__)
	// verify assumation about queue size at compile time
	STATIC_CHECK( IsPowerOfTwoCompileTime<eMaxWorkQueueJobsHighPriority>::IsPowerOfTwo, ERROR_MAX_JOB_QUEUE_SIZE__HIGH_PRIORITY_IS_NOT_POWER_OF_TWO );
	STATIC_CHECK( IsPowerOfTwoCompileTime<eMaxWorkQueueJobsRegularPriority>::IsPowerOfTwo, ERROR_MAX_JOB_QUEUE_SIZE_REGULAR_PRIORITY_IS_NOT_POWER_OF_TWO );
	STATIC_CHECK( IsPowerOfTwoCompileTime<eMaxWorkQueueJobsLowPriority>::IsPowerOfTwo, ERROR_MAX_JOB_QUEUE_SIZE__LOW_PRIORITY_IS_NOT_POWER_OF_TWO );


	// init job queues
	jobInfoBlocks[eHighPriority]				= (JobManager::SInfoBlock*)CryModuleMemalign(eMaxWorkQueueJobsHighPriority * sizeof(JobManager::SInfoBlock), 128);
	jobInfoBlockStates[eHighPriority]		= (JobManager::SJobQueueSlotState*)CryModuleMemalign(eMaxWorkQueueJobsHighPriority * sizeof(JobManager::SJobQueueSlotState), 128);
	memset(jobInfoBlocks[eHighPriority], 0, eMaxWorkQueueJobsHighPriority*sizeof(JobManager::SInfoBlock));
	memset(jobInfoBlockStates[eHighPriority], 0, eMaxWorkQueueJobsHighPriority*sizeof(JobManager::SJobQueueSlotState));

	jobInfoBlocks[eRegularPriority]					= (JobManager::SInfoBlock*)CryModuleMemalign(eMaxWorkQueueJobsRegularPriority * sizeof(JobManager::SInfoBlock), 128);
	jobInfoBlockStates[eRegularPriority]		= (JobManager::SJobQueueSlotState*)CryModuleMemalign(eMaxWorkQueueJobsRegularPriority * sizeof(JobManager::SJobQueueSlotState), 128);
	memset(jobInfoBlocks[eRegularPriority], 0, eMaxWorkQueueJobsRegularPriority * sizeof(JobManager::SInfoBlock));
	memset(jobInfoBlockStates[eRegularPriority], 0, eMaxWorkQueueJobsRegularPriority * sizeof(JobManager::SJobQueueSlotState));

	jobInfoBlocks[eLowPriority]					= (JobManager::SInfoBlock*)CryModuleMemalign(eMaxWorkQueueJobsLowPriority * sizeof(JobManager::SInfoBlock), 128);
	jobInfoBlockStates[eLowPriority]		= (JobManager::SJobQueueSlotState*)CryModuleMemalign(eMaxWorkQueueJobsLowPriority * sizeof(JobManager::SJobQueueSlotState), 128);
	memset(jobInfoBlocks[eLowPriority], 0, eMaxWorkQueueJobsLowPriority * sizeof(JobManager::SInfoBlock));
	memset(jobInfoBlockStates[eLowPriority], 0, eMaxWorkQueueJobsLowPriority * sizeof(JobManager::SJobQueueSlotState));
	
	// init queue pos objects
	push.lockObtained								= 0;
	push.jobQueue[eHighPriority]		= jobInfoBlocks[eHighPriority];
	push.jobQueue[eRegularPriority]	= jobInfoBlocks[eRegularPriority];
	push.jobQueue[eLowPriority]			= jobInfoBlocks[eLowPriority];
	push.jobQueueStates[eHighPriority]		= jobInfoBlockStates[eHighPriority];
	push.jobQueueStates[eRegularPriority]	= jobInfoBlockStates[eRegularPriority];
	push.jobQueueStates[eLowPriority]			= jobInfoBlockStates[eLowPriority];	
	push.index											= 0;
			
	pull.lockObtained								= 0;
	pull.jobQueue[eHighPriority]		= jobInfoBlocks[eHighPriority];
	pull.jobQueue[eRegularPriority]	= jobInfoBlocks[eRegularPriority];
	pull.jobQueue[eLowPriority]			= jobInfoBlocks[eLowPriority];
	pull.jobQueueStates[eHighPriority]		= jobInfoBlockStates[eHighPriority];
	pull.jobQueueStates[eRegularPriority]	= jobInfoBlockStates[eRegularPriority];
	pull.jobQueueStates[eLowPriority]			= jobInfoBlockStates[eLowPriority];
	pull.index											= 0;		
#endif
}

///////////////////////////////////////////////////////////////////////////////
template<int nMaxWorkQueueJobsHighPriority,int nMaxWorkQueueJobsRegularPriority, int nMaxWorkQueueJobsLowPriority>
uint32 JobManager::SJobQueue<nMaxWorkQueueJobsHighPriority,nMaxWorkQueueJobsRegularPriority,nMaxWorkQueueJobsLowPriority>::GetMaxWorkerQueueJobs( uint32 nPriorityLevel )
{
	switch(nPriorityLevel)
	{
	case eHighPriority:			return eMaxWorkQueueJobsHighPriority;
	case eRegularPriority:	return eMaxWorkQueueJobsRegularPriority;
	case eLowPriority:			return eMaxWorkQueueJobsLowPriority;
	default:
		return ~0;
	}
}

#endif // __SPU_JOBSTRUCTS_H
